##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the new API for making and checking interface declarations
"""
import unittest

from zope.interface.interface import InterfaceClass
from zope.interface.tests import MissingSomeAttrs
from zope.interface.tests import OptimizationTestMixin
from zope.interface.tests import SubclassableMixin
from zope.interface.tests.test_interface import \
    NameAndModuleComparisonTestsMixin


# pylint:disable=inherit-non-class,too-many-lines,protected-access
# pylint:disable=blacklisted-name,attribute-defined-outside-init

IFoo = InterfaceClass('IFoo')


class Foo:
    pass


class FooImplementedNone:
    __implemented__ = None


class FooNoCall:
    __implemented__ = None

    def __call__(self):
        raise NotImplementedError()


class _Py3ClassAdvice:

    def _run_generated_code(
        self, code, globs, locs, fails_under_py3k=True,
    ):
        # pylint:disable=exec-used,no-member
        import warnings
        with warnings.catch_warnings(record=True) as _:
            warnings.resetwarnings()

            try:
                exec(code, globs, locs)
            except TypeError:
                return False
            else:
                if fails_under_py3k:
                    self.fail("Didn't raise TypeError")
            return None


class NamedTests(unittest.TestCase):

    def test_class(self):
        from zope.interface.declarations import named

        @named('foo')
        class Foo:
            pass

        self.assertEqual(
            Foo.__component_name__, 'foo'
        )  # pylint:disable=no-member

    def test_function(self):
        from zope.interface.declarations import named

        @named('foo')
        def doFoo(o):
            raise NotImplementedError()

        self.assertEqual(doFoo.__component_name__, 'foo')

    def test_instance(self):
        from zope.interface.declarations import named

        class Foo:
            pass
        foo = Foo()
        named('foo')(foo)

        self.assertEqual(
            foo.__component_name__, 'foo'
        )  # pylint:disable=no-member


class EmptyDeclarationTests(unittest.TestCase):
    # Tests that should pass for all objects that are empty
    # declarations. This includes a Declaration explicitly created
    # that way, and the empty ImmutableDeclaration.
    def _getEmpty(self):
        from zope.interface.declarations import Declaration
        return Declaration()

    def test___iter___empty(self):
        decl = self._getEmpty()
        self.assertEqual(list(decl), [])

    def test_flattened_empty(self):
        from zope.interface.interface import Interface
        decl = self._getEmpty()
        self.assertEqual(list(decl.flattened()), [Interface])

    def test___contains___empty(self):
        from zope.interface.interface import Interface
        decl = self._getEmpty()
        self.assertNotIn(Interface, decl)

    def test_extends_empty(self):
        from zope.interface.interface import Interface
        decl = self._getEmpty()
        self.assertTrue(decl.extends(Interface))
        self.assertTrue(decl.extends(Interface, strict=True))

    def test_interfaces_empty(self):
        decl = self._getEmpty()
        iface_list = list(decl.interfaces())
        self.assertEqual(iface_list, [])

    def test___sro___(self):
        from zope.interface.interface import Interface
        decl = self._getEmpty()
        self.assertEqual(decl.__sro__, (decl, Interface,))

    def test___iro___(self):
        from zope.interface.interface import Interface
        decl = self._getEmpty()
        self.assertEqual(decl.__iro__, (Interface,))

    def test_get(self):
        decl = self._getEmpty()
        self.assertIsNone(decl.get('attr'))
        self.assertEqual(decl.get('abc', 'def'), 'def')
        # It's a positive cache only (when it even exists)
        # so this added nothing.
        self.assertFalse(decl._v_attrs)

    def test_changed_w_existing__v_attrs(self):
        decl = self._getEmpty()
        decl._v_attrs = object()
        decl.changed(decl)
        self.assertFalse(decl._v_attrs)


class DeclarationTests(EmptyDeclarationTests):

    def _getTargetClass(self):
        from zope.interface.declarations import Declaration
        return Declaration

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_ctor_no_bases(self):
        decl = self._makeOne()
        self.assertEqual(list(decl.__bases__), [])

    def test_ctor_w_interface_in_bases(self):
        IFoo = InterfaceClass('IFoo')
        decl = self._makeOne(IFoo)
        self.assertEqual(list(decl.__bases__), [IFoo])

    def test_ctor_w_implements_in_bases(self):
        from zope.interface.declarations import Implements
        impl = Implements()
        decl = self._makeOne(impl)
        self.assertEqual(list(decl.__bases__), [impl])

    def test_changed_wo_existing__v_attrs(self):
        decl = self._makeOne()
        decl.changed(decl)  # doesn't raise
        self.assertIsNone(decl._v_attrs)

    def test___contains__w_self(self):
        decl = self._makeOne()
        self.assertNotIn(decl, decl)

    def test___contains__w_unrelated_iface(self):
        IFoo = InterfaceClass('IFoo')
        decl = self._makeOne()
        self.assertNotIn(IFoo, decl)

    def test___contains__w_base_interface(self):
        IFoo = InterfaceClass('IFoo')
        decl = self._makeOne(IFoo)
        self.assertIn(IFoo, decl)

    def test___iter___single_base(self):
        IFoo = InterfaceClass('IFoo')
        decl = self._makeOne(IFoo)
        self.assertEqual(list(decl), [IFoo])

    def test___iter___multiple_bases(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        decl = self._makeOne(IFoo, IBar)
        self.assertEqual(list(decl), [IFoo, IBar])

    def test___iter___inheritance(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar', (IFoo,))
        decl = self._makeOne(IBar)
        self.assertEqual(list(decl), [IBar])  # IBar.interfaces() omits bases

    def test___iter___w_nested_sequence_overlap(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        decl = self._makeOne(IBar, (IFoo, IBar))
        self.assertEqual(list(decl), [IBar, IFoo])

    def test_flattened_single_base(self):
        from zope.interface.interface import Interface
        IFoo = InterfaceClass('IFoo')
        decl = self._makeOne(IFoo)
        self.assertEqual(list(decl.flattened()), [IFoo, Interface])

    def test_flattened_multiple_bases(self):
        from zope.interface.interface import Interface
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        decl = self._makeOne(IFoo, IBar)
        self.assertEqual(list(decl.flattened()), [IFoo, IBar, Interface])

    def test_flattened_inheritance(self):
        from zope.interface.interface import Interface
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar', (IFoo,))
        decl = self._makeOne(IBar)
        self.assertEqual(list(decl.flattened()), [IBar, IFoo, Interface])

    def test_flattened_w_nested_sequence_overlap(self):
        from zope.interface.interface import Interface
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        # This is the same as calling ``Declaration(IBar, IFoo, IBar)``
        # which doesn't make much sense, but here it is. In older
        # versions of zope.interface, the __iro__ would have been
        # IFoo, IBar, Interface, which especially makes no sense.
        decl = self._makeOne(IBar, (IFoo, IBar))
        # Note that decl.__iro__ has IFoo first.
        self.assertEqual(list(decl.flattened()), [IBar, IFoo, Interface])

    def test___sub___unrelated_interface(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        before = self._makeOne(IFoo)
        after = before - IBar
        self.assertIsInstance(after, self._getTargetClass())
        self.assertEqual(list(after), [IFoo])

    def test___sub___related_interface(self):
        IFoo = InterfaceClass('IFoo')
        before = self._makeOne(IFoo)
        after = before - IFoo
        self.assertEqual(list(after), [])

    def test___sub___related_interface_by_inheritance(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar', (IFoo,))
        before = self._makeOne(IBar)
        after = before - IBar
        self.assertEqual(list(after), [])

    def test___add___unrelated_interface(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        before = self._makeOne(IFoo)
        after = before + IBar
        self.assertIsInstance(after, self._getTargetClass())
        self.assertEqual(list(after), [IFoo, IBar])

    def test___add___related_interface(self):
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        IBaz = InterfaceClass('IBaz')
        before = self._makeOne(IFoo, IBar)
        other = self._makeOne(IBar, IBaz)
        after = before + other
        self.assertEqual(list(after), [IFoo, IBar, IBaz])

    def test___add___overlapping_interface(self):
        # The derived interfaces end up with higher priority, and
        # don't produce a C3 resolution order violation. This
        # example produced a C3 error, and the resulting legacy order
        # used to be wrong ([IBase, IDerived] instead of
        # the other way).
        from zope.interface import Interface
        from zope.interface import ro
        from zope.interface.tests.test_ro import C3Setting

        IBase = InterfaceClass('IBase')
        IDerived = InterfaceClass('IDerived', (IBase,))

        with C3Setting(ro.C3.STRICT_IRO, True):
            base = self._makeOne(IBase)
            after = base + IDerived

        self.assertEqual(after.__iro__, (IDerived, IBase, Interface))
        self.assertEqual(after.__bases__, (IDerived, IBase))
        self.assertEqual(list(after), [IDerived, IBase])

    def test___add___overlapping_interface_implementedBy(self):
        # Like test___add___overlapping_interface, but pulling
        # in a realistic example. This one previously produced a
        # C3 error, but the resulting legacy order was (somehow)
        # correct.
        from zope.interface import Interface
        from zope.interface import implementedBy
        from zope.interface import implementer
        from zope.interface import ro
        from zope.interface.tests.test_ro import C3Setting

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IBase)
        class Base:
            pass

        with C3Setting(ro.C3.STRICT_IRO, True):
            after = implementedBy(Base) + IDerived

        self.assertEqual(after.__sro__, (after, IDerived, IBase, Interface))
        self.assertEqual(after.__bases__, (IDerived, IBase))
        self.assertEqual(list(after), [IDerived, IBase])


class TestImmutableDeclaration(EmptyDeclarationTests):

    def _getTargetClass(self):
        from zope.interface.declarations import _ImmutableDeclaration
        return _ImmutableDeclaration

    def _getEmpty(self):
        from zope.interface.declarations import _empty
        return _empty

    def test_pickle(self):
        import pickle
        copied = pickle.loads(pickle.dumps(self._getEmpty()))
        self.assertIs(copied, self._getEmpty())

    def test_singleton(self):
        self.assertIs(
            self._getTargetClass()(),
            self._getEmpty()
        )

    def test__bases__(self):
        self.assertEqual(self._getEmpty().__bases__, ())

    def test_change__bases__(self):
        empty = self._getEmpty()
        empty.__bases__ = ()
        self.assertEqual(self._getEmpty().__bases__, ())

        with self.assertRaises(TypeError):
            empty.__bases__ = (1,)

    def test_dependents(self):
        empty = self._getEmpty()
        deps = empty.dependents
        self.assertEqual({}, deps)
        # Doesn't change the return.
        deps[1] = 2
        self.assertEqual({}, empty.dependents)

    def test_changed(self):
        # Does nothing, has no visible side-effects
        self._getEmpty().changed(None)

    def test_extends_always_false(self):
        self.assertFalse(self._getEmpty().extends(self))
        self.assertFalse(self._getEmpty().extends(self, strict=True))
        self.assertFalse(self._getEmpty().extends(self, strict=False))

    def test_get_always_default(self):
        self.assertIsNone(self._getEmpty().get('name'))
        self.assertEqual(self._getEmpty().get('name', 42), 42)

    def test_v_attrs(self):
        decl = self._getEmpty()
        self.assertEqual(decl._v_attrs, {})

        decl._v_attrs['attr'] = 42
        self.assertEqual(decl._v_attrs, {})
        self.assertIsNone(decl.get('attr'))

        attrs = decl._v_attrs = {}
        attrs['attr'] = 42
        self.assertEqual(decl._v_attrs, {})
        self.assertIsNone(decl.get('attr'))


class TestImplements(NameAndModuleComparisonTestsMixin,
                     unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import Implements
        return Implements

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def _makeOneToCompare(self):
        from zope.interface.declarations import implementedBy

        class A:
            pass

        return implementedBy(A)

    def test_ctor_no_bases(self):
        impl = self._makeOne()
        self.assertEqual(impl.inherit, None)
        self.assertEqual(impl.declared, ())
        self.assertEqual(impl.__name__, '?')
        self.assertEqual(list(impl.__bases__), [])

    def test___repr__(self):
        impl = self._makeOne()
        impl.__name__ = 'Testing'
        self.assertEqual(repr(impl), 'classImplements(Testing)')

    def test___reduce__(self):
        from zope.interface.declarations import implementedBy
        impl = self._makeOne()
        self.assertEqual(impl.__reduce__(), (implementedBy, (None,)))

    def test_sort(self):
        from zope.interface.declarations import implementedBy

        class A:
            pass

        class B:
            pass

        IFoo = InterfaceClass('IFoo')

        self.assertEqual(implementedBy(A), implementedBy(A))
        self.assertEqual(hash(implementedBy(A)), hash(implementedBy(A)))
        self.assertLess(implementedBy(A), None)
        self.assertGreater(
            None,
            implementedBy(A)
        )
        self.assertLess(implementedBy(A), implementedBy(B))
        self.assertGreater(implementedBy(A), IFoo)
        self.assertLessEqual(implementedBy(A), implementedBy(B))
        self.assertGreaterEqual(implementedBy(A), IFoo)
        self.assertNotEqual(implementedBy(A), IFoo)

    def test_proxy_equality(self):
        # https://github.com/zopefoundation/zope.interface/issues/55
        from zope.interface.declarations import implementedBy

        class Proxy:
            def __init__(self, wrapped):
                self._wrapped = wrapped

            def __getattr__(self, name):
                raise NotImplementedError()

            def __eq__(self, other):
                return self._wrapped == other

            def __ne__(self, other):
                return self._wrapped != other

        class A:
            pass

        class B:
            pass

        implementedByA = implementedBy(A)
        implementedByB = implementedBy(B)
        proxy = Proxy(implementedByA)

        # The order of arguments to the operators matters,
        # test both
        self.assertEqual(
            implementedByA,
            implementedByA
        )
        self.assertNotEqual(implementedByA, implementedByB)
        self.assertNotEqual(implementedByB, implementedByA)

        self.assertEqual(proxy, implementedByA)
        self.assertEqual(implementedByA, proxy)
        self.assertEqual(proxy, implementedByA)
        self.assertEqual(implementedByA, proxy)

        self.assertNotEqual(proxy, implementedByB)
        self.assertNotEqual(implementedByB, proxy)

    def test_changed_deletes_super_cache(self):
        impl = self._makeOne()
        self.assertIsNone(impl._super_cache)
        self.assertNotIn('_super_cache', impl.__dict__)

        impl._super_cache = 42
        self.assertIn('_super_cache', impl.__dict__)

        impl.changed(None)
        self.assertIsNone(impl._super_cache)
        self.assertNotIn('_super_cache', impl.__dict__)

    def test_changed_does_not_add_super_cache(self):
        impl = self._makeOne()
        self.assertIsNone(impl._super_cache)
        self.assertNotIn('_super_cache', impl.__dict__)

        impl.changed(None)
        self.assertIsNone(impl._super_cache)
        self.assertNotIn('_super_cache', impl.__dict__)


class Test_implementedByFallback(unittest.TestCase):

    def _getTargetClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import implementedByFallback
        return implementedByFallback

    _getFallbackClass = _getTargetClass

    def _callFUT(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_dictless_wo_existing_Implements_wo_registrations(self):
        class Foo:
            __slots__ = ('__implemented__',)
        foo = Foo()
        foo.__implemented__ = None
        self.assertEqual(list(self._callFUT(foo)), [])

    def test_dictless_wo_existing_Implements_cant_assign___implemented__(self):

        class Foo:
            def _get_impl(self):
                raise NotImplementedError()

            def _set_impl(self, val):
                raise TypeError

            __implemented__ = property(_get_impl, _set_impl)

            def __call__(self):
                # act like a factory
                raise NotImplementedError()

        foo = Foo()
        self.assertRaises(TypeError, self._callFUT, foo)

    def test_dictless_wo_existing_Implements_w_registrations(self):
        from zope.interface import declarations

        class Foo:
            __slots__ = ('__implemented__',)

        foo = Foo()
        foo.__implemented__ = None
        reg = object()
        with _MonkeyDict(declarations,
                         'BuiltinImplementationSpecifications') as specs:
            specs[foo] = reg
            self.assertIs(self._callFUT(foo), reg)

    def test_dictless_w_existing_Implements(self):
        from zope.interface.declarations import Implements
        impl = Implements()

        class Foo:
            __slots__ = ('__implemented__',)

        foo = Foo()
        foo.__implemented__ = impl
        self.assertIs(self._callFUT(foo), impl)

    def test_dictless_w_existing_not_Implements(self):

        class Foo:
            __slots__ = ('__implemented__',)

        foo = Foo()
        IFoo = InterfaceClass('IFoo')
        foo.__implemented__ = (IFoo,)
        self.assertEqual(list(self._callFUT(foo)), [IFoo])

    def test_w_existing_attr_as_Implements(self):
        from zope.interface.declarations import Implements
        impl = Implements()

        class Foo:
            __implemented__ = impl

        self.assertIs(self._callFUT(Foo), impl)

    def test_builtins_added_to_cache(self):
        from zope.interface import declarations
        from zope.interface.declarations import Implements
        with _MonkeyDict(declarations,
                         'BuiltinImplementationSpecifications') as specs:
            self.assertEqual(list(self._callFUT(tuple)), [])
            self.assertEqual(list(self._callFUT(list)), [])
            self.assertEqual(list(self._callFUT(dict)), [])
            for typ in (tuple, list, dict):
                spec = specs[typ]
                self.assertIsInstance(spec, Implements)
                self.assertEqual(repr(spec),
                                 'classImplements(%s)'
                                 % (typ.__name__,))

    def test_builtins_w_existing_cache(self):
        from zope.interface import declarations
        t_spec, l_spec, d_spec = object(), object(), object()
        with _MonkeyDict(declarations,
                         'BuiltinImplementationSpecifications') as specs:
            specs[tuple] = t_spec
            specs[list] = l_spec
            specs[dict] = d_spec
            self.assertIs(self._callFUT(tuple), t_spec)
            self.assertIs(self._callFUT(list), l_spec)
            self.assertIs(self._callFUT(dict), d_spec)

    def test_oldstyle_class_no_assertions(self):
        # TODO: Figure out P3 story

        class Foo:
            pass

        self.assertEqual(list(self._callFUT(Foo)), [])

    def test_no_assertions(self):
        # TODO: Figure out P3 story

        class Foo:
            pass

        self.assertEqual(list(self._callFUT(Foo)), [])

    def test_w_None_no_bases_not_factory(self):

        class Foo:
            __implemented__ = None

        foo = Foo()
        self.assertRaises(TypeError, self._callFUT, foo)

    def test_w_None_no_bases_w_factory(self):
        from zope.interface.declarations import objectSpecificationDescriptor
        from zope.interface.tests.test_declarations import FooNoCall

        foo = FooNoCall()
        foo.__name__ = 'foo'
        spec = self._callFUT(foo)
        self.assertEqual(spec.__name__,
                         'zope.interface.tests.test_declarations.foo')
        self.assertIs(spec.inherit, foo)
        self.assertIs(foo.__implemented__, spec)
        self.assertIs(
            foo.__providedBy__, objectSpecificationDescriptor
        )  # pylint:disable=no-member
        self.assertNotIn('__provides__', foo.__dict__)

    def test_w_None_no_bases_w_class(self):
        from zope.interface.declarations import ClassProvides
        from zope.interface.tests.test_declarations import FooImplementedNone

        spec = self._callFUT(FooImplementedNone)
        self.assertEqual(
            spec.__name__,
            'zope.interface.tests.test_declarations.FooImplementedNone')
        self.assertIs(spec.inherit, FooImplementedNone)
        self.assertIs(FooImplementedNone.__implemented__, spec)
        self.assertIsInstance(
            FooImplementedNone.__providedBy__, ClassProvides
        )  # pylint:disable=no-member
        self.assertIsInstance(
            FooImplementedNone.__provides__, ClassProvides
        )  # pylint:disable=no-member
        self.assertEqual(
            FooImplementedNone.__provides__, FooImplementedNone.__providedBy__
        )  # pylint:disable=no-member

    def test_w_existing_Implements(self):
        from zope.interface.declarations import Implements
        impl = Implements()

        class Foo:
            __implemented__ = impl

        self.assertIs(self._callFUT(Foo), impl)

    def test_super_when_base_implements_interface(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IBase)
        class Base:
            pass

        @implementer(IDerived)
        class Derived(Base):
            pass

        self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
        sup = super(Derived, Derived)
        self.assertEqual(list(self._callFUT(sup)), [IBase])

    def test_super_when_base_implements_interface_diamond(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IBase)
        class Base:
            pass

        class Child1(Base):
            pass

        class Child2(Base):
            pass

        @implementer(IDerived)
        class Derived(Child1, Child2):
            pass

        self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
        sup = super(Derived, Derived)
        self.assertEqual(list(self._callFUT(sup)), [IBase])

    def test_super_when_parent_implements_interface_diamond(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        class Base:
            pass

        class Child1(Base):
            pass

        @implementer(IBase)
        class Child2(Base):
            pass

        @implementer(IDerived)
        class Derived(Child1, Child2):
            pass

        self.assertEqual(
            Derived.__mro__, (Derived, Child1, Child2, Base, object)
        )
        self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
        sup = super(Derived, Derived)
        fut = self._callFUT(sup)
        self.assertEqual(list(fut), [IBase])
        self.assertIsNone(fut._dependents)

    def test_super_when_base_doesnt_implement_interface(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        class Base:
            pass

        @implementer(IDerived)
        class Derived(Base):
            pass

        self.assertEqual(list(self._callFUT(Derived)), [IDerived])

        sup = super(Derived, Derived)
        self.assertEqual(list(self._callFUT(sup)), [])

    def test_super_when_base_is_object(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IDerived)
        class Derived:
            pass

        self.assertEqual(list(self._callFUT(Derived)), [IDerived])

        sup = super(Derived, Derived)
        self.assertEqual(list(self._callFUT(sup)), [])

    def test_super_multi_level_multi_inheritance(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IM1(Interface):
            pass

        class IM2(Interface):
            pass

        class IDerived(IBase):
            pass

        class IUnrelated(Interface):
            pass

        @implementer(IBase)
        class Base:
            pass

        @implementer(IM1)
        class M1(Base):
            pass

        @implementer(IM2)
        class M2(Base):
            pass

        @implementer(IDerived, IUnrelated)
        class Derived(M1, M2):
            pass

        d = Derived
        sd = super(Derived, Derived)
        sm1 = super(M1, Derived)
        sm2 = super(M2, Derived)

        self.assertEqual(list(self._callFUT(d)),
                         [IDerived, IUnrelated, IM1, IBase, IM2])
        self.assertEqual(list(self._callFUT(sd)),
                         [IM1, IBase, IM2])
        self.assertEqual(list(self._callFUT(sm1)),
                         [IM2, IBase])
        self.assertEqual(list(self._callFUT(sm2)),
                         [IBase])


class Test_implementedBy(Test_implementedByFallback,
                         OptimizationTestMixin):
    # Repeat tests for C optimizations

    def _getTargetClass(self):
        from zope.interface.declarations import implementedBy
        return implementedBy


class _ImplementsTestMixin:
    FUT_SETS_PROVIDED_BY = True

    def _callFUT(self, cls, iface):
        # Declare that *cls* implements *iface*; return *cls*
        raise NotImplementedError

    def _check_implementer(self, Foo,
                           orig_spec=None,
                           spec_name=__name__ + '.Foo',
                           inherit="not given"):
        from zope.interface.declarations import ClassProvides
        IFoo = InterfaceClass('IFoo')

        returned = self._callFUT(Foo, IFoo)

        self.assertIs(returned, Foo)
        spec = Foo.__implemented__
        if orig_spec is not None:
            self.assertIs(spec, orig_spec)

        self.assertEqual(spec.__name__,
                         spec_name)
        inherit = Foo if inherit == "not given" else inherit
        self.assertIs(spec.inherit, inherit)
        self.assertIs(Foo.__implemented__, spec)
        if self.FUT_SETS_PROVIDED_BY:
            self.assertIsInstance(Foo.__providedBy__, ClassProvides)
            self.assertIsInstance(Foo.__provides__, ClassProvides)
            self.assertEqual(Foo.__provides__, Foo.__providedBy__)

        return Foo, IFoo

    def test_class(self):

        class Foo:
            pass

        self._check_implementer(Foo)


class Test_classImplementsOnly(_ImplementsTestMixin, unittest.TestCase):
    FUT_SETS_PROVIDED_BY = False

    def _callFUT(self, cls, iface):
        from zope.interface.declarations import classImplementsOnly
        classImplementsOnly(cls, iface)
        return cls

    def test_w_existing_Implements(self):
        from zope.interface.declarations import Implements
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        impl = Implements(IFoo)
        impl.declared = (IFoo,)

        class Foo:
            __implemented__ = impl

        impl.inherit = Foo
        self._callFUT(Foo, IBar)
        # Same spec, now different values
        self.assertIs(Foo.__implemented__, impl)
        self.assertEqual(impl.inherit, None)
        self.assertEqual(impl.declared, (IBar,))

    def test_class(self):
        from zope.interface.declarations import Implements
        IBar = InterfaceClass('IBar')
        old_spec = Implements(IBar)

        class Foo:
            __implemented__ = old_spec

        self._check_implementer(Foo, old_spec, '?', inherit=None)

    def test_redundant_with_super_still_implements(self):
        Base, IBase = self._check_implementer(
            type('Foo', (object,), {}),
            inherit=None,
        )

        class Child(Base):
            pass

        self._callFUT(Child, IBase)
        self.assertTrue(IBase.implementedBy(Child))


class Test_classImplements(_ImplementsTestMixin, unittest.TestCase):

    def _callFUT(self, cls, iface):
        from zope.interface.declarations import classImplements
        result = classImplements(
            cls, iface
        )  # pylint:disable=assignment-from-no-return
        self.assertIsNone(result)
        return cls

    def __check_implementer_redundant(self, Base):
        # If we @implementer exactly what was already present, we write no
        # declared attributes on the parent (we still set everything, though)
        Base, IBase = self._check_implementer(Base)

        class Child(Base):
            pass

        returned = self._callFUT(Child, IBase)
        self.assertIn('__implemented__', returned.__dict__)
        self.assertNotIn('__providedBy__', returned.__dict__)
        self.assertIn('__provides__', returned.__dict__)

        spec = Child.__implemented__
        self.assertEqual(spec.declared, ())
        self.assertEqual(spec.inherit, Child)

        self.assertTrue(IBase.providedBy(Child()))

    def test_redundant_implementer_empty_class_declarations(self):

        class Foo:
            pass

        self.__check_implementer_redundant(Foo)

    def test_redundant_implementer_Interface(self):
        from zope.interface import Interface
        from zope.interface import implementedBy
        from zope.interface import ro
        from zope.interface.tests.test_ro import C3Setting

        class Foo:
            pass

        with C3Setting(ro.C3.STRICT_IRO, False):
            self._callFUT(Foo, Interface)
            self.assertEqual(list(implementedBy(Foo)), [Interface])

            class Baz(Foo):
                pass

            self._callFUT(Baz, Interface)
            self.assertEqual(list(implementedBy(Baz)), [Interface])

    def _order_for_two(self, applied_first, applied_second):
        return (applied_first, applied_second)

    def test_w_existing_Implements(self):
        from zope.interface.declarations import Implements
        IFoo = InterfaceClass('IFoo')
        IBar = InterfaceClass('IBar')
        impl = Implements(IFoo)
        impl.declared = (IFoo,)

        class Foo:
            __implemented__ = impl

        impl.inherit = Foo
        self._callFUT(Foo, IBar)
        # Same spec, now different values
        self.assertIs(Foo.__implemented__, impl)
        self.assertEqual(impl.inherit, Foo)
        self.assertEqual(impl.declared,
                         self._order_for_two(IFoo, IBar))

    def test_w_existing_Implements_w_bases(self):
        from zope.interface.declarations import Implements
        IRoot = InterfaceClass('IRoot')
        ISecondRoot = InterfaceClass('ISecondRoot')
        IExtendsRoot = InterfaceClass('IExtendsRoot', (IRoot,))

        impl_root = Implements.named('Root', IRoot)
        impl_root.declared = (IRoot,)

        class Root1:
            __implemented__ = impl_root

        class Root2:
            __implemented__ = impl_root

        impl_extends_root = Implements.named('ExtendsRoot1', IExtendsRoot)
        impl_extends_root.declared = (IExtendsRoot,)

        class ExtendsRoot(Root1, Root2):
            __implemented__ = impl_extends_root

        impl_extends_root.inherit = ExtendsRoot

        self._callFUT(ExtendsRoot, ISecondRoot)
        # Same spec, now different values
        self.assertIs(ExtendsRoot.__implemented__, impl_extends_root)
        self.assertEqual(impl_extends_root.inherit, ExtendsRoot)
        self.assertEqual(impl_extends_root.declared,
                         self._order_for_two(IExtendsRoot, ISecondRoot,))
        self.assertEqual(
            impl_extends_root.__bases__,
            self._order_for_two(IExtendsRoot, ISecondRoot) + (impl_root,)
        )


class Test_classImplementsFirst(Test_classImplements):

    def _callFUT(self, cls, iface):
        from zope.interface.declarations import classImplementsFirst
        result = classImplementsFirst(
            cls, iface
        )  # pylint:disable=assignment-from-no-return
        self.assertIsNone(result)
        return cls

    def _order_for_two(self, applied_first, applied_second):
        return (applied_second, applied_first)


class Test__implements_advice(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import _implements_advice
        return _implements_advice(*args, **kw)

    def test_no_existing_implements(self):
        from zope.interface.declarations import Implements
        from zope.interface.declarations import classImplements
        IFoo = InterfaceClass('IFoo')

        class Foo:
            __implements_advice_data__ = ((IFoo,), classImplements)

        self._callFUT(Foo)
        self.assertNotIn('__implements_advice_data__', Foo.__dict__)
        self.assertIsInstance(
            Foo.__implemented__, Implements
        )  # pylint:disable=no-member
        self.assertEqual(
            list(Foo.__implemented__), [IFoo]
        )  # pylint:disable=no-member


class Test_implementer(Test_classImplements):

    def _getTargetClass(self):
        from zope.interface.declarations import implementer
        return implementer

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def _callFUT(self, cls, *ifaces):
        decorator = self._makeOne(*ifaces)
        return decorator(cls)

    def test_nonclass_cannot_assign_attr(self):
        IFoo = InterfaceClass('IFoo')
        decorator = self._makeOne(IFoo)
        self.assertRaises(TypeError, decorator, object())

    def test_nonclass_can_assign_attr(self):
        IFoo = InterfaceClass('IFoo')
        from zope.interface.tests.test_declarations import Foo
        foo = Foo()
        decorator = self._makeOne(IFoo)
        returned = decorator(foo)
        self.assertIs(returned, foo)
        spec = foo.__implemented__  # pylint:disable=no-member
        self.assertEqual(
            spec.__name__, 'zope.interface.tests.test_declarations.?'
        )
        self.assertIsNone(spec.inherit,)
        self.assertIs(foo.__implemented__, spec)  # pylint:disable=no-member

    def test_does_not_leak_on_unique_classes(self):
        # Make sure nothing is hanging on to the class or Implements
        # object after they go out of scope. There was briefly a bug
        # in 5.x that caused SpecificationBase._bases (in C) to not be
        # traversed or cleared.
        # https://github.com/zopefoundation/zope.interface/issues/216
        import gc

        IFoo = InterfaceClass('IFoo')

        begin_count = len(gc.get_objects())

        for _ in range(1900):
            class TestClass:
                pass

            self._callFUT(TestClass, IFoo)

        gc.collect()

        end_count = len(gc.get_objects())

        # How many new objects might still be around? In all currently
        # tested interpreters, there aren't any, so our counts should
        # match exactly. When the bug existed, in a steady state, the loop
        # would grow by two objects each iteration
        fudge_factor = 0
        self.assertLessEqual(end_count, begin_count + fudge_factor)


class Test_implementer_only(Test_classImplementsOnly):

    def _getTargetClass(self):
        from zope.interface.declarations import implementer_only
        return implementer_only

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def _callFUT(self, cls, iface):
        decorator = self._makeOne(iface)
        return decorator(cls)

    def test_function(self):
        IFoo = InterfaceClass('IFoo')
        decorator = self._makeOne(IFoo)

        def _function():
            raise NotImplementedError()

        self.assertRaises(ValueError, decorator, _function)

    def test_method(self):
        IFoo = InterfaceClass('IFoo')
        decorator = self._makeOne(IFoo)

        class Bar:
            def _method(self):
                raise NotImplementedError()

        self.assertRaises(ValueError, decorator, Bar._method)


class ProvidesClassTests(unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import ProvidesClass
        return ProvidesClass

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_simple_class_one_interface(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        spec = self._makeOne(Foo, IFoo)
        self.assertEqual(list(spec), [IFoo])

    def test___reduce__(self):
        from zope.interface.declarations import Provides  # the function
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        spec = self._makeOne(Foo, IFoo)
        klass, args = spec.__reduce__()
        self.assertIs(klass, Provides)
        self.assertEqual(args, (Foo, IFoo))

    def test___get___class(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        spec = self._makeOne(Foo, IFoo)
        Foo.__provides__ = spec
        self.assertIs(Foo.__provides__, spec)

    def test___get___instance(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        spec = self._makeOne(Foo, IFoo)
        Foo.__provides__ = spec

        def _test():
            foo = Foo()
            return foo.__provides__

        self.assertRaises(AttributeError, _test)


class ProvidesClassStrictTests(ProvidesClassTests):
    # Tests that require the strict C3 resolution order.

    def _getTargetClass(self):
        ProvidesClass = super()._getTargetClass()

        class StrictProvides(ProvidesClass):
            def _do_calculate_ro(self, base_mros):
                return ProvidesClass._do_calculate_ro(
                    self, base_mros=base_mros, strict=True,
                )

        return StrictProvides

    def test_overlapping_interfaces_corrected(self):
        # Giving Provides(cls, IFace), where IFace is already
        # provided by cls, doesn't produce invalid resolution orders.
        from zope.interface import Interface
        from zope.interface import implementedBy
        from zope.interface import implementer

        class IBase(Interface):
            pass

        @implementer(IBase)
        class Base:
            pass

        spec = self._makeOne(Base, IBase)
        self.assertEqual(spec.__sro__, (
            spec,
            implementedBy(Base),
            IBase,
            implementedBy(object),
            Interface
        ))


class TestProvidesClassRepr(unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import ProvidesClass
        return ProvidesClass

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test__repr__(self):
        IFoo = InterfaceClass("IFoo")
        assert IFoo.__name__ == 'IFoo'
        assert IFoo.__module__ == __name__
        assert repr(IFoo) == f'<InterfaceClass {__name__}.IFoo>'

        IBar = InterfaceClass("IBar")

        inst = self._makeOne(type(self), IFoo, IBar)
        self.assertEqual(
            repr(inst),
            "directlyProvides(TestProvidesClassRepr, IFoo, IBar)"
        )

    def test__repr__module_provides_typical_use(self):
        # as created through a ``moduleProvides()`` statement
        # in a module body
        from zope.interface.tests import dummy
        provides = dummy.__provides__  # pylint:disable=no-member
        self.assertEqual(
            repr(provides),
            "directlyProvides("
            "sys.modules['zope.interface.tests.dummy'], "
            "IDummyModule)"
        )

    def test__repr__module_after_pickle(self):
        # It doesn't matter, these objects can't be pickled.
        import pickle

        from zope.interface.tests import dummy
        provides = dummy.__provides__  # pylint:disable=no-member
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            with self.assertRaises(pickle.PicklingError):
                pickle.dumps(provides, proto)

    def test__repr__directlyProvides_module(self):
        import sys

        from zope.interface.declarations import alsoProvides
        from zope.interface.declarations import directlyProvides
        from zope.interface.tests import dummy
        from zope.interface.tests.test_declarations import IFoo

        IBar = InterfaceClass('IBar')

        orig_provides = dummy.__provides__  # pylint:disable=no-member
        del dummy.__provides__  # pylint:disable=no-member
        self.addCleanup(setattr, dummy, '__provides__', orig_provides)

        directlyProvides(dummy, IFoo)
        provides = dummy.__provides__  # pylint:disable=no-member

        self.assertEqual(
            repr(provides),
            "directlyProvides(sys.modules['zope.interface.tests.dummy'], IFoo)"
        )

        alsoProvides(dummy, IBar)
        provides = dummy.__provides__  # pylint:disable=no-member

        self.assertEqual(
            repr(provides),
            "directlyProvides("
            "sys.modules['zope.interface.tests.dummy'], "
            "IFoo, IBar)"
        )

        # If we make this module also provide IFoo and IBar, then the repr
        # lists both names.
        my_module = sys.modules[__name__]
        assert not hasattr(my_module, '__provides__')

        directlyProvides(my_module, IFoo, IBar)
        self.addCleanup(delattr, my_module, '__provides__')
        self.assertIs(my_module.__provides__, provides)
        self.assertEqual(
            repr(provides),
            "directlyProvides(('zope.interface.tests.dummy', "
            "'interface.tests.test_declarations'), "
            "IFoo, IBar)"
        )

    def test__repr__module_provides_cached_shared(self):
        from zope.interface.declarations import ModuleType
        IFoo = InterfaceClass("IFoo")

        inst = self._makeOne(ModuleType, IFoo)
        inst._v_module_names += ('some.module',)
        inst._v_module_names += ('another.module',)
        self.assertEqual(
            repr(inst),
            "directlyProvides(('some.module', 'another.module'), IFoo)"
        )

    def test__repr__duplicate_names(self):
        IFoo = InterfaceClass("IFoo", __module__='mod1')
        IFoo2 = InterfaceClass("IFoo", __module__='mod2')
        IBaz = InterfaceClass("IBaz")

        inst = self._makeOne(type(self), IFoo, IBaz, IFoo2)
        self.assertEqual(
            repr(inst),
            "directlyProvides(TestProvidesClassRepr, IFoo, IBaz, mod2.IFoo)"
        )

    def test__repr__implementedBy_in_interfaces(self):
        from zope.interface import Interface
        from zope.interface import implementedBy

        class IFoo(Interface):
            "Does nothing"

        class Bar:
            "Does nothing"

        impl = implementedBy(type(self))

        inst = self._makeOne(Bar, IFoo, impl)
        self.assertEqual(
            repr(inst),
            'directlyProvides('
            'Bar, IFoo, '
            'classImplements(TestProvidesClassRepr))'
        )

    def test__repr__empty_interfaces(self):
        inst = self._makeOne(type(self))
        self.assertEqual(
            repr(inst),
            'directlyProvides(TestProvidesClassRepr)',
        )

    def test__repr__non_class(self):

        def str___dont_call_me():
            self.fail("Should not call str")

        class Object:
            __bases__ = ()
            __str__ = str___dont_call_me

            def __repr__(self):
                return '<Object>'

        inst = self._makeOne(Object())
        self.assertEqual(
            repr(inst),
            'directlyProvides(<Object>)',
        )

    def test__repr__providedBy_from_class(self):
        from zope.interface.declarations import implementer
        from zope.interface.declarations import providedBy
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        inst = providedBy(Foo())
        self.assertEqual(
            repr(inst),
            'classImplements(Foo, IFoo)'
        )

    def test__repr__providedBy_alsoProvides(self):
        from zope.interface.declarations import alsoProvides
        from zope.interface.declarations import implementer
        from zope.interface.declarations import providedBy
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        alsoProvides(foo, IBar)

        inst = providedBy(foo)
        self.assertEqual(
            repr(inst),
            "directlyProvides(Foo, IBar, classImplements(Foo, IFoo))"
        )


class Test_Provides(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import Provides
        return Provides(*args, **kw)

    def test_no_cached_spec(self):
        from zope.interface import declarations
        IFoo = InterfaceClass("IFoo")
        cache = {}

        class Foo:
            pass

        with _Monkey(declarations, InstanceDeclarations=cache):
            spec = self._callFUT(Foo, IFoo)
        self.assertEqual(list(spec), [IFoo])
        self.assertIs(cache[(Foo, IFoo)], spec)

    def test_w_cached_spec(self):
        from zope.interface import declarations
        IFoo = InterfaceClass("IFoo")
        prior = object()

        class Foo:
            pass

        cache = {(Foo, IFoo): prior}
        with _Monkey(declarations, InstanceDeclarations=cache):
            spec = self._callFUT(Foo, IFoo)
        self.assertIs(spec, prior)


class Test_directlyProvides(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import directlyProvides
        return directlyProvides(*args, **kw)

    def test_w_normal_object(self):
        from zope.interface.declarations import ProvidesClass
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        obj = Foo()
        self._callFUT(obj, IFoo)
        self.assertIsInstance(
            obj.__provides__, ProvidesClass
        )  # pylint:disable=no-member
        self.assertEqual(
            list(obj.__provides__), [IFoo]
        )  # pylint:disable=no-member

    def test_w_class(self):
        from zope.interface.declarations import ClassProvides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        self._callFUT(Foo, IFoo)
        self.assertIsInstance(
            Foo.__provides__, ClassProvides
        )  # pylint:disable=no-member
        self.assertEqual(
            list(Foo.__provides__), [IFoo]
        )  # pylint:disable=no-member

    def test_w_classless_object(self):
        from zope.interface.declarations import ProvidesClass
        IFoo = InterfaceClass("IFoo")
        the_dict = {}

        class Foo:
            def __getattribute__(self, name):
                # Emulate object w/o any class
                if name == '__class__':
                    return None
                raise NotImplementedError(name)

            def __setattr__(self, name, value):
                the_dict[name] = value

        obj = Foo()
        self._callFUT(obj, IFoo)
        self.assertIsInstance(the_dict['__provides__'], ProvidesClass)
        self.assertEqual(list(the_dict['__provides__']), [IFoo])


class Test_alsoProvides(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import alsoProvides
        return alsoProvides(*args, **kw)

    def test_wo_existing_provides(self):
        from zope.interface.declarations import ProvidesClass
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        obj = Foo()
        self._callFUT(obj, IFoo)
        self.assertIsInstance(
            obj.__provides__, ProvidesClass
        )  # pylint:disable=no-member
        self.assertEqual(
            list(obj.__provides__), [IFoo]
        )  # pylint:disable=no-member

    def test_w_existing_provides(self):
        from zope.interface.declarations import ProvidesClass
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        class Foo:
            pass

        obj = Foo()
        directlyProvides(obj, IFoo)
        self._callFUT(obj, IBar)
        self.assertIsInstance(
            obj.__provides__, ProvidesClass
        )  # pylint:disable=no-member
        self.assertEqual(
            list(obj.__provides__), [IFoo, IBar]
        )  # pylint:disable=no-member


class Test_noLongerProvides(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import noLongerProvides
        return noLongerProvides(*args, **kw)

    def test_wo_existing_provides(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        obj = Foo()
        self._callFUT(obj, IFoo)
        self.assertEqual(
            list(obj.__provides__), []
        )  # pylint:disable=no-member

    def test_w_existing_provides_hit(self):
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        obj = Foo()
        directlyProvides(obj, IFoo)
        self._callFUT(obj, IFoo)
        self.assertEqual(
            list(obj.__provides__), []
        )  # pylint:disable=no-member

    def test_w_existing_provides_miss(self):
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        class Foo:
            pass

        obj = Foo()
        directlyProvides(obj, IFoo)
        self._callFUT(obj, IBar)
        self.assertEqual(
            list(obj.__provides__), [IFoo]
        )  # pylint:disable=no-member

    def test_w_iface_implemented_by_class(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        obj = Foo()
        self.assertRaises(ValueError, self._callFUT, obj, IFoo)


class ClassProvidesBaseFallbackTests(unittest.TestCase):

    def _getTargetClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import ClassProvidesBaseFallback
        return ClassProvidesBaseFallback

    def _makeOne(self, klass, implements):
        # Don't instantiate directly:  the C version can't have attributes
        # assigned.

        class Derived(self._getTargetClass()):
            def __init__(self, k, i):
                self._cls = k
                self._implements = i

        return Derived(klass, implements)

    def test_w_same_class_via_class(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo)
        self.assertIs(Foo.__provides__, cpbp)

    def test_w_same_class_via_instance(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        foo = Foo()
        Foo.__provides__ = self._makeOne(Foo, IFoo)
        self.assertIs(foo.__provides__, IFoo)

    def test_w_different_class(self):
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        class Bar(Foo):
            pass

        bar = Bar()
        Foo.__provides__ = self._makeOne(Foo, IFoo)
        self.assertRaises(AttributeError, getattr, Bar, '__provides__')
        self.assertRaises(AttributeError, getattr, bar, '__provides__')


class ClassProvidesBaseTests(
    OptimizationTestMixin,
    ClassProvidesBaseFallbackTests,
    SubclassableMixin,
):
    # Repeat tests for C optimizations

    def _getTargetClass(self):
        from zope.interface.declarations import ClassProvidesBase
        return ClassProvidesBase

    def _getFallbackClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import ClassProvidesBaseFallback
        return ClassProvidesBaseFallback


class ClassProvidesTests(unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import ClassProvides
        return ClassProvides

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_w_simple_metaclass(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        @implementer(IFoo)
        class Foo:
            pass

        cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar)
        self.assertIs(Foo.__provides__, cp)
        self.assertEqual(list(Foo().__provides__), [IFoo])

    def test___reduce__(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        @implementer(IFoo)
        class Foo:
            pass

        cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar)
        self.assertEqual(cp.__reduce__(),
                         (type(cp), (Foo, type(Foo), IBar)))


class ClassProvidesStrictTests(ClassProvidesTests):
    # Tests that require the strict C3 resolution order.

    def _getTargetClass(self):
        ClassProvides = super()._getTargetClass()

        class StrictClassProvides(ClassProvides):
            def _do_calculate_ro(self, base_mros):
                return ClassProvides._do_calculate_ro(
                    self, base_mros=base_mros, strict=True
                )

        return StrictClassProvides

    def test_overlapping_interfaces_corrected(self):
        # Giving ClassProvides(cls, metaclass, IFace), where IFace is already
        # provided by metacls, doesn't produce invalid resolution orders.
        from zope.interface import Interface
        from zope.interface import implementedBy
        from zope.interface import implementer

        class IBase(Interface):
            pass

        @implementer(IBase)
        class metaclass(type):
            pass

        cls = metaclass(
            'cls',
            (object,),
            {}
        )

        spec = self._makeOne(cls, metaclass, IBase)
        self.assertEqual(spec.__sro__, (
            spec,
            implementedBy(metaclass),
            IBase,
            implementedBy(type),
            implementedBy(object),
            Interface
        ))


class TestClassProvidesRepr(unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import ClassProvides
        return ClassProvides

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test__repr__empty(self):
        inst = self._makeOne(type(self), type)
        self.assertEqual(
            repr(inst),
            "directlyProvides(TestClassProvidesRepr)"
        )

    def test__repr__providing_one(self):
        from zope.interface import Interface

        class IFoo(Interface):
            "Does nothing"

        inst = self._makeOne(type(self), type, IFoo)
        self.assertEqual(
            repr(inst),
            "directlyProvides(TestClassProvidesRepr, IFoo)"
        )

    def test__repr__duplicate_names(self):
        IFoo = InterfaceClass("IFoo", __module__='mod1')
        IFoo2 = InterfaceClass("IFoo", __module__='mod2')
        IBaz = InterfaceClass("IBaz")

        inst = self._makeOne(type(self), type, IFoo, IBaz, IFoo2)
        self.assertEqual(
            repr(inst),
            "directlyProvides(TestClassProvidesRepr, IFoo, IBaz, mod2.IFoo)"
        )

    def test__repr__implementedBy(self):
        from zope.interface.declarations import implementedBy
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        inst = implementedBy(Foo)
        self.assertEqual(
            repr(inst),
            'classImplements(Foo, IFoo)'
        )

    def test__repr__implementedBy_generic_callable(self):
        from zope.interface.declarations import implementedBy

        # We can't get a __name__ by default, so we get a
        # module name and a question mark
        class Callable:
            def __call__(self):
                return self

        inst = implementedBy(Callable())
        self.assertEqual(
            repr(inst),
            f'classImplements({__name__}.?)'
        )

        c = Callable()
        c.__name__ = 'Callable'
        inst = implementedBy(c)
        self.assertEqual(
            repr(inst),
            'classImplements(Callable)'
        )


class Test_directlyProvidedBy(unittest.TestCase):

    def _callFUT(self, *args, **kw):
        from zope.interface.declarations import directlyProvidedBy
        return directlyProvidedBy(*args, **kw)

    def test_wo_declarations_in_class_or_instance(self):

        class Foo:
            pass

        foo = Foo()
        self.assertEqual(list(self._callFUT(foo)), [])

    def test_w_declarations_in_class_but_not_instance(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        self.assertEqual(list(self._callFUT(foo)), [])

    def test_w_declarations_in_instance_but_not_class(self):
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        foo = Foo()
        directlyProvides(foo, IFoo)
        self.assertEqual(list(self._callFUT(foo)), [IFoo])

    def test_w_declarations_in_instance_and_class(self):
        from zope.interface.declarations import directlyProvides
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        directlyProvides(foo, IBar)
        self.assertEqual(list(self._callFUT(foo)), [IBar])


class Test_provider(unittest.TestCase):

    def _getTargetClass(self):
        from zope.interface.declarations import provider
        return provider

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_w_class(self):
        from zope.interface.declarations import ClassProvides
        IFoo = InterfaceClass("IFoo")

        @self._makeOne(IFoo)
        class Foo:
            pass

        self.assertIsInstance(
            Foo.__provides__, ClassProvides
        )  # pylint:disable=no-member
        self.assertEqual(
            list(Foo.__provides__), [IFoo]
        )  # pylint:disable=no-member


class Test_moduleProvides(unittest.TestCase):
    # pylint:disable=exec-used

    def test_called_from_function(self):
        from zope.interface.declarations import moduleProvides
        IFoo = InterfaceClass("IFoo")
        globs = {'__name__': 'zope.interface.tests.foo',
                 'moduleProvides': moduleProvides, 'IFoo': IFoo}
        locs = {}
        CODE = "\n".join([
            'def foo():',
            '    moduleProvides(IFoo)'
        ])
        exec(CODE, globs, locs)
        foo = locs['foo']
        self.assertRaises(TypeError, foo)

    def test_called_from_class(self):
        from zope.interface.declarations import moduleProvides
        IFoo = InterfaceClass("IFoo")
        globs = {'__name__': 'zope.interface.tests.foo',
                 'moduleProvides': moduleProvides, 'IFoo': IFoo}
        locs = {}
        CODE = "\n".join([
            'class Foo(object):',
            '    moduleProvides(IFoo)',
        ])
        with self.assertRaises(TypeError):
            exec(CODE, globs, locs)

    def test_called_once_from_module_scope(self):
        from zope.interface.declarations import moduleProvides
        IFoo = InterfaceClass("IFoo")
        globs = {'__name__': 'zope.interface.tests.foo',
                 'moduleProvides': moduleProvides, 'IFoo': IFoo}
        CODE = "\n".join([
            'moduleProvides(IFoo)',
        ])
        exec(CODE, globs)
        spec = globs['__provides__']
        self.assertEqual(list(spec), [IFoo])

    def test_called_twice_from_module_scope(self):
        from zope.interface.declarations import moduleProvides
        IFoo = InterfaceClass("IFoo")
        globs = {'__name__': 'zope.interface.tests.foo',
                 'moduleProvides': moduleProvides, 'IFoo': IFoo}

        CODE = "\n".join([
            'moduleProvides(IFoo)',
            'moduleProvides(IFoo)',
        ])
        with self.assertRaises(TypeError):
            exec(CODE, globs)


class Test_getObjectSpecificationFallback(unittest.TestCase):

    def _getFallbackClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import getObjectSpecificationFallback
        return getObjectSpecificationFallback

    _getTargetClass = _getFallbackClass

    def _callFUT(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_wo_existing_provides_classless(self):
        the_dict = {}

        class Foo:
            def __getattribute__(self, name):
                # Emulate object w/o any class
                if name == '__class__':
                    raise AttributeError(name)
                try:
                    return the_dict[name]
                except KeyError:
                    raise AttributeError(name)

            def __setattr__(self, name, value):
                raise NotImplementedError()

        foo = Foo()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [])

    def test_existing_provides_is_spec(self):
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")

        def foo():
            raise NotImplementedError()

        directlyProvides(foo, IFoo)
        spec = self._callFUT(foo)
        self.assertIs(spec, foo.__provides__)  # pylint:disable=no-member

    def test_existing_provides_is_not_spec(self):

        def foo():
            raise NotImplementedError()

        foo.__provides__ = object()  # not a valid spec
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [])

    def test_existing_provides(self):
        from zope.interface.declarations import directlyProvides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        foo = Foo()
        directlyProvides(foo, IFoo)
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [IFoo])

    def test_wo_provides_on_class_w_implements(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [IFoo])

    def test_wo_provides_on_class_wo_implements(self):

        class Foo:
            pass

        foo = Foo()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [])

    def test_catches_only_AttributeError_on_provides(self):
        MissingSomeAttrs.test_raises(
            self, self._callFUT, expected_missing='__provides__'
        )

    def test_catches_only_AttributeError_on_class(self):
        MissingSomeAttrs.test_raises(
            self,
            self._callFUT,
            expected_missing='__class__',
            __provides__=None,
        )

    def test_raises_AttrError_w_provides_fails_type_check_AttrError(self):
        # isinstance(ob.__provides__, SpecificationBase) is not
        # protected inside any kind of block.

        class Foo:
            __provides__ = MissingSomeAttrs(AttributeError)

        # isinstance() ignores AttributeError on __class__
        self._callFUT(Foo())

    def test_raises_AttrError_w_provides_fails_type_check_RuntimeError(self):
        # isinstance(ob.__provides__, SpecificationBase) is not
        # protected inside any kind of block.
        class Foo:
            __provides__ = MissingSomeAttrs(RuntimeError)

        with self.assertRaises(RuntimeError) as exc:
            self._callFUT(Foo())

        self.assertEqual('__class__', exc.exception.args[0])


class Test_getObjectSpecification(Test_getObjectSpecificationFallback,
                                  OptimizationTestMixin):
    # Repeat tests for C optimizations

    def _getTargetClass(self):
        from zope.interface.declarations import getObjectSpecification
        return getObjectSpecification


class Test_providedByFallback(unittest.TestCase):

    def _getFallbackClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import providedByFallback
        return providedByFallback

    _getTargetClass = _getFallbackClass

    def _callFUT(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_wo_providedBy_on_class_wo_implements(self):

        class Foo:
            pass

        foo = Foo()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [])

    def test_w_providedBy_valid_spec(self):
        from zope.interface.declarations import Provides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = Provides(Foo, IFoo)
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [IFoo])

    def test_w_providedBy_invalid_spec(self):

        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = object()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [])

    def test_w_providedBy_invalid_spec_class_w_implements(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = object()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [IFoo])

    def test_w_providedBy_invalid_spec_w_provides_no_provides_on_class(self):

        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = object()
        expected = foo.__provides__ = object()
        spec = self._callFUT(foo)
        self.assertIs(spec, expected)

    def test_w_providedBy_invalid_spec_w_provides_diff_provides_on_class(self):

        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = object()
        expected = foo.__provides__ = object()
        Foo.__provides__ = object()
        spec = self._callFUT(foo)
        self.assertIs(spec, expected)

    def test_w_providedBy_invalid_spec_w_provides_same_provides_on_class(self):
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")

        @implementer(IFoo)
        class Foo:
            pass

        foo = Foo()
        foo.__providedBy__ = object()
        foo.__provides__ = Foo.__provides__ = object()
        spec = self._callFUT(foo)
        self.assertEqual(list(spec), [IFoo])

    def test_super_when_base_implements_interface(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IBase)
        class Base:
            pass

        @implementer(IDerived)
        class Derived(Base):
            pass

        derived = Derived()
        self.assertEqual(list(self._callFUT(derived)), [IDerived, IBase])

        sup = super(Derived, derived)
        fut = self._callFUT(sup)
        self.assertIsNone(fut._dependents)
        self.assertEqual(list(fut), [IBase])

    def test_super_when_base_doesnt_implement_interface(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        class Base:
            pass

        @implementer(IDerived)
        class Derived(Base):
            pass

        derived = Derived()
        self.assertEqual(list(self._callFUT(derived)), [IDerived])

        sup = super(Derived, derived)
        self.assertEqual(list(self._callFUT(sup)), [])

    def test_super_when_base_is_object(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IDerived)
        class Derived:
            pass

        derived = Derived()
        self.assertEqual(list(self._callFUT(derived)), [IDerived])

        sup = super(Derived, derived)
        fut = self._callFUT(sup)
        self.assertIsNone(fut._dependents)
        self.assertEqual(list(fut), [])

    def test_super_when_object_directly_provides(self):
        from zope.interface import Interface
        from zope.interface.declarations import directlyProvides
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IDerived(IBase):
            pass

        @implementer(IBase)
        class Base:
            pass

        class Derived(Base):
            pass

        derived = Derived()
        self.assertEqual(list(self._callFUT(derived)), [IBase])

        directlyProvides(derived, IDerived)
        self.assertEqual(list(self._callFUT(derived)), [IDerived, IBase])

        sup = super(Derived, derived)
        fut = self._callFUT(sup)
        self.assertIsNone(fut._dependents)
        self.assertEqual(list(fut), [IBase])

    def test_super_multi_level_multi_inheritance(self):
        from zope.interface import Interface
        from zope.interface.declarations import implementer

        class IBase(Interface):
            pass

        class IM1(Interface):
            pass

        class IM2(Interface):
            pass

        class IDerived(IBase):
            pass

        class IUnrelated(Interface):
            pass

        @implementer(IBase)
        class Base:
            pass

        @implementer(IM1)
        class M1(Base):
            pass

        @implementer(IM2)
        class M2(Base):
            pass

        @implementer(IDerived, IUnrelated)
        class Derived(M1, M2):
            pass

        d = Derived()
        sd = super(Derived, d)
        sm1 = super(M1, d)
        sm2 = super(M2, d)

        self.assertEqual(list(self._callFUT(d)),
                         [IDerived, IUnrelated, IM1, IBase, IM2])
        self.assertEqual(list(self._callFUT(sd)),
                         [IM1, IBase, IM2])
        self.assertEqual(list(self._callFUT(sm1)),
                         [IM2, IBase])
        self.assertEqual(list(self._callFUT(sm2)),
                         [IBase])

    def test_catches_only_AttributeError_on_providedBy(self):
        MissingSomeAttrs.test_raises(self, self._callFUT,
                                     expected_missing='__providedBy__',
                                     __class__=object)

    def test_catches_only_AttributeError_on_class(self):
        # isinstance() tries to get the __class__, which is non-obvious,
        # so it must be protected too.
        MissingSomeAttrs.test_raises(
            self, self._callFUT, expected_missing='__class__')


class Test_providedBy(Test_providedByFallback,
                      OptimizationTestMixin):
    # Repeat tests for C optimizations

    def _getTargetClass(self):
        from zope.interface.declarations import providedBy
        return providedBy


class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase):

    def _getFallbackClass(self):
        # pylint:disable=no-name-in-module
        from zope.interface.declarations import \
            ObjectSpecificationDescriptorFallback
        return ObjectSpecificationDescriptorFallback

    _getTargetClass = _getFallbackClass

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_accessed_via_class(self):
        from zope.interface.declarations import Provides
        IFoo = InterfaceClass("IFoo")

        class Foo:
            pass

        Foo.__provides__ = Provides(Foo, IFoo)
        Foo.__providedBy__ = self._makeOne()
        self.assertEqual(list(Foo.__providedBy__), [IFoo])

    def test_accessed_via_inst_wo_provides(self):
        from zope.interface.declarations import Provides
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")

        @implementer(IFoo)
        class Foo:
            pass

        Foo.__provides__ = Provides(Foo, IBar)
        Foo.__providedBy__ = self._makeOne()
        foo = Foo()
        self.assertEqual(list(foo.__providedBy__), [IFoo])

    def test_accessed_via_inst_w_provides(self):
        from zope.interface.declarations import Provides
        from zope.interface.declarations import directlyProvides
        from zope.interface.declarations import implementer
        IFoo = InterfaceClass("IFoo")
        IBar = InterfaceClass("IBar")
        IBaz = InterfaceClass("IBaz")

        @implementer(IFoo)
        class Foo:
            pass

        Foo.__provides__ = Provides(Foo, IBar)
        Foo.__providedBy__ = self._makeOne()
        foo = Foo()
        directlyProvides(foo, IBaz)
        self.assertEqual(list(foo.__providedBy__), [IBaz, IFoo])

    def test_arbitrary_exception_accessing_provides_not_caught(self):

        class MyException(Exception):
            pass

        class Foo:
            __providedBy__ = self._makeOne()

            @property
            def __provides__(self):
                raise MyException

        foo = Foo()
        with self.assertRaises(MyException):
            getattr(foo, '__providedBy__')

    def test_AttributeError_accessing_provides_caught(self):

        class MyException(Exception):
            pass

        class Foo:
            __providedBy__ = self._makeOne()

            @property
            def __provides__(self):
                raise AttributeError

        foo = Foo()
        provided = getattr(foo, '__providedBy__')
        self.assertIsNotNone(provided)

    def test_None_in__provides__overrides(self):
        from zope.interface import Interface
        from zope.interface import implementer

        class IFoo(Interface):
            pass

        @implementer(IFoo)
        class Foo:

            @property
            def __provides__(self):
                return None

        Foo.__providedBy__ = self._makeOne()

        provided = getattr(Foo(), '__providedBy__')
        self.assertIsNone(provided)


class ObjectSpecificationDescriptorTests(
        ObjectSpecificationDescriptorFallbackTests,
        OptimizationTestMixin,
        SubclassableMixin,
):
    # Repeat tests for C optimizations

    def _getTargetClass(self):
        from zope.interface.declarations import ObjectSpecificationDescriptor
        return ObjectSpecificationDescriptor


# Test _normalizeargs through its callers.


class _Monkey:
    # context-manager for replacing module names in the scope of a test.
    def __init__(self, module, **kw):
        self.module = module
        self.to_restore = {key: getattr(module, key) for key in kw}
        for key, value in kw.items():
            setattr(module, key, value)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for key, value in self.to_restore.items():
            setattr(self.module, key, value)


class _MonkeyDict:
    # context-manager for restoring a dict w/in a module in the scope of a
    # test.
    def __init__(self, module, attrname, **kw):
        self.module = module
        self.target = getattr(module, attrname)
        self.to_restore = self.target.copy()
        self.target.clear()
        self.target.update(kw)

    def __enter__(self):
        return self.target

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.target.clear()
        self.target.update(self.to_restore)
